﻿-- Author      : Thorwald Odin
-- Create Date : 12/7/2013 1:44:46 PM

--[[
	@title LibRecycler-1.0
	@author Lightsfury of US-Uther
	@notes Lightweight object recycler.
--]]

--[[
Copyright (c) 2010 Robert Beam

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
--]]

local LibRecycler_Major, LibRecycler_Minor = 'LibRecycler-1.0', 2;

assert(LibStub, LibRecycler_Major .. ' requires LibStub!');

local lib, oldMinor = LibStub:NewLibrary(LibRecycler_Major, LibRecycler_Minor);

if not lib then -- No upgrade needed
	return;
end

-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: LibStub, geterrorhandler

local assert, type, rawget, rawset, next, setmetatable, getmetatable = assert, type, rawget, rawset, next, setmetatable, getmetatable;

oldMinor = oldMinor or 0;

lib.Recyclers = lib.Recyclers or {};
lib.Recycler = lib.Recycler or {};

if oldMinor < 2 then

	lib.Recycler_Meta = {
		__index = lib.Recycler,
	};
	
	lib.FreeObject_MetaFunc = function(Object, Key)
		geterrorhandler()('Attempt to index `' .. tostring(Key) .. '` on a released ' .. LibRecycler_Major .. ' object.');
		return nil;
	end
	
	lib.FreeObject_Meta = {
		__index = lib.FreeObject_MetaFunc,
		__newindex = lib.FreeObject_MetaFunc,
	};
	
	lib.FreeObjects_Meta = {
		__mode = 'k',
	};
	
	lib.Recycler.Acquire = function(self)
		assert(type(self) == 'table'); -- Sanity checks.
		assert(type(self.FreeObjects) == 'table');
		
		local Object = next(self.FreeObjects);
		
		if not Object then -- Did we get an object?
			Object = self.NewObject(); -- No, so create one.
		else -- Yes, so ...
			self.FreeObjects[Object] = nil; -- Remove the object from the pool, ...
			local meta = rawget(Object, '__meta'); -- And rotate the meta tables.
			setmetatable(Object, meta);
			rawset(Object, '__meta', nil);
		end
		
		return Object; -- And we are done.
	end
	
	lib.Recycler.Release = function(self, Object)
		assert(type(self) == 'table'); -- Sanity checks.
		assert(type(Object) ~= 'nil');
		
		local meta = getmetatable(Object); -- Grab the application-defined meta table.
		
		if self.ResetObject then -- Do we have an object reset button?
			self.ResetObject(Object); -- Yes, so press it.
		end
		
		rawset(Object, '__meta', meta); -- Save the application-defined meta table.
		
		setmetatable(Object, lib.FreeObject_Meta); -- Apply the library meta table.
		
		self.FreeObjects[Object] = true; -- Return to the pool.
	end
	
	lib.NewRecycler = function(self, Name, New, Reset)
		assert(type(self) == 'table'); -- Sanity checks.
		assert(type(Name) == 'string');
		assert(type(New) == 'function');
		assert(type(Reset) == 'function' or type(Reset) == 'nil');
		
		assert(type(self.Recyclers[Name]) == 'nil', 'Recycler of type "' .. Name .. '" is already registered!');
		
		local Recycler = setmetatable({}, self.Recycler_Meta); -- Start with a basic Recycler object.
		Recycler.FreeObjects = setmetatable({}, self.FreeObjects_Meta); -- Add the object pool.
		Recycler.NewObject = New; -- Register the new button.
		Recycler.ResetObject = Reset; -- Register the reset button.
		rawset(self.Recyclers, Name, Recycler); -- Register the recycler with the library.
		
		return Recycler; -- And return the recycler.
	end
	
	lib.GetRecycler = function(self, Name)
		assert(type(self) == 'table'); -- Sanity checks.
		assert(type(Name) == 'string');
		
		return rawget(self.Recyclers, Name);
	end
	
	lib.Library_Meta = {
		__index = lib.Recyclers,
		__call = lib.GetRecycler,
	};
end

